package com.cloudfun.campusshare.device.protocol.command;

import com.cloudfun.campusshare.util.ByteUtil;
import lombok.Data;
import org.apache.commons.lang3.ArrayUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by Huqin on 2020/1/13
 */
@Data
public class Frame {

    /**
     * 默认帧头，固定2个字节，FF FF
     */
    public final static byte[] DEFAULT_HEAD = new byte[]{-1, -1};

    /**
     * FF替换为FF55
     */
    public final static byte STR_55 = ByteUtil.hexStringToByte("55")[0];

    /**
     * 帧头
     */
    private byte[] head;

    /**
     * 包括有效负荷和校验和在内的整个帧长度的字节数，固定1个字节
     */
    private byte length;

    /**
     * 有效负荷
     */
    private FrameBody content;

    /**
     * 校验和
     */
    private byte checkSum;

    /**
     * 附加校验和，生成的命令帧没有这个属性
     */
    private byte addChecksum;

    private final byte[] frameData;

    /**
     * 此构造方法为服务端构造命令帧使用
     *
     * @param type
     * @param data
     */
    protected Frame(byte type, byte[] data) {
        this.head = DEFAULT_HEAD;
        this.content = new FrameBody(type, data);
        // 计算帧长
        this.length = (byte) (content.length() + 1);
        this.frameData = this.toArray();
    }

    /**
     * 此构造方法为服务端解析设备回传数据帧使用
     *
     * @param head
     * @param length
     * @param content
     * @param checkSum
     * @param addChecksum
     */
    private Frame(byte[] head, byte length, byte[] content, byte checkSum, byte addChecksum) {
        this.head = head;
        this.length = length;
        this.content = new FrameBody(content);
        this.checkSum = checkSum;
        this.addChecksum = addChecksum;
        List<byte[]> bytes = new ArrayList<>();
        bytes.add(head);
        bytes.add(new byte[]{length});
        bytes.add(content);
        bytes.add(new byte[]{checkSum});
        bytes.add(new byte[]{addChecksum});
        this.frameData = ByteUtil.merge(bytes);
    }

//    public static void main(String[] args) {
//        String frame = "ffff16000000000000740f5a000000000000000000000000f3f4ffff16000000000000740f5a000000000000000000000000f3f4";
//        System.out.println(parse(ByteUtil.hexStringToByte(frame)));
//    }

    public static List<Frame> parse(byte[] data) {
        List<Integer> frameStarts = new ArrayList<>();
        for (int i = 0; i < data.length; i++) {
            if (data[i] == -1 && data[i + 1] == -1 && i < data.length - 1) {
                frameStarts.add(i);
            }
        }
        int frameStart = 0;
        List<Frame> res = new ArrayList<>();
        for (int i = 0; i < frameStarts.size(); i++) {
            if (frameStarts.get(i) != frameStart) {
                byte[] frameData = ArrayUtils.subarray(data, frameStart, frameStarts.get(i));
                res.add(doParse(frameData));
                frameStart = frameStarts.get(i);
            }
        }
        byte[] frameData = ArrayUtils.subarray(data, frameStart, data.length);
        res.add(doParse(frameData));
        return res;
    }

    private static Frame doParse(byte[] data) {
        // 消息头校验
        byte[] actualHead = ArrayUtils.subarray(data, 0, 2);
        if (!Arrays.equals(DEFAULT_HEAD, actualHead)) {
            throw new RuntimeException("invalid frame head:" + ByteUtil.bytesToHexString(actualHead));
        }
        // 校验和校验
        byte expectedCheckSum;
        int checkSumLen;
        if (data[data.length - 3] == -1 && data[data.length - 2] == STR_55) {
            expectedCheckSum = data[data.length - 3];
            checkSumLen = 2;
        } else {
            checkSumLen = 1;
            expectedCheckSum = data[data.length - 2];
        }
        // 校验和计算内容为：帧长和帧体
        byte actualCheckSum = ByteUtil.checkSum(data, 2, data.length - 1 - checkSumLen);
        if (expectedCheckSum != actualCheckSum) {
            throw new RuntimeException("invalid check sum. expected: " + expectedCheckSum + ", actual: " + actualCheckSum);
        }
        // 长度检查
        byte expectedLength = data[2];
        byte actualLength = 0;
        // 帧长为：有效负荷和校验和
        byte[] contentAndCheckSum = new byte[]{};
        for (int i = 3; i < data.length - 1; i++) {
            contentAndCheckSum = ArrayUtils.add(contentAndCheckSum, data[i]);
            if (i + 1 < data.length - 1 && data[i] == -1 && data[i + 1] == STR_55) {
                i++;
            }
            actualLength++;
        }
        if (actualLength != expectedLength) {
            throw new RuntimeException("invalid message. expected frame length: " + expectedLength + ", actual length: " + actualLength);
        }
        byte[] content = ArrayUtils.subarray(contentAndCheckSum, 0, contentAndCheckSum.length - 1);
        byte checkSum = contentAndCheckSum[contentAndCheckSum.length - 1];
        return new Frame(actualHead, actualLength, content, checkSum, data[data.length - 1]);
    }

    private byte[] toArray() {
        List<byte[]> bytes = new ArrayList<>();
        bytes.add(head);

        byte[] lengthAndContent = ArrayUtils.addAll(new byte[]{length}, content.toArray());
        byte[] res = new byte[]{};
        // 将FF替换为FF55 并计算校验和
        for (int i = 0; i < lengthAndContent.length; i++) {
            res = ArrayUtils.add(res, lengthAndContent[i]);
            if (lengthAndContent[i] == (byte) -1) {
                res = ArrayUtils.add(res, STR_55);
            }
        }
        bytes.add(res);
        // 校验和
        this.checkSum = ByteUtil.checkSum(res, 0, res.length);
        byte[] checkSumByt;
        if (checkSum == (byte) -1) {
            checkSumByt = new byte[]{checkSum, STR_55};
        } else {
            checkSumByt = new byte[]{checkSum};
        }
        bytes.add(checkSumByt);

        // 附加校验和
        byte[] result = ByteUtil.merge(bytes);
        this.addChecksum = ByteUtil.checkSum(result, 0, result.length);
        return result;
    }

    @Override
    public String toString() {
        return ByteUtil.bytesToHexString(this.frameData);
    }

    public String additionalCheckSumStr() {
        return ByteUtil.bytesToHexString(new byte[]{this.addChecksum});
    }

}
